package eu.smoothit.sis.admin.old;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.faces.application.FacesMessage;
import com.icesoft.faces.component.ext.HtmlDataTable;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;

import eu.smoothit.sis.admin.util.DTOComparator;

/**
 *super class that provides view, update, add and delete functions of date in
 * Table style
 */
public abstract class DataTableAbstract<E> {

	// Constants used for assigning attributes
	// ----------------------------------------------------------------------------------
	private static final String DEFAULT_SORT_FIELD = "id";
	private static final boolean DEFAULT_SORT_ASCENDING = false;
	private static final boolean DEFAULT_SELECT_MULTIPLE = true;
	private static final boolean DEFAULT_SELECT_ALL = false;
	private static final int DEFAULT_ADD_COUNT = 1;
	private static final int DEFAULT_TABLE_ROWS = 10;

	// Properties of date table
	// ---------------------------------------------------------------------------------
	protected String sortField = DEFAULT_SORT_FIELD;
	protected boolean sortAscending = DEFAULT_SORT_ASCENDING;
	private boolean selectMultiple = DEFAULT_SELECT_MULTIPLE;
	private boolean selectAll = DEFAULT_SELECT_ALL;
	private int addCount = DEFAULT_ADD_COUNT;
	protected HtmlDataTable dataTable;
	protected List<E> dataList;
	protected Map<Integer, Boolean> selectedRows = new HashMap<Integer, Boolean>();
	protected Map<Integer, Boolean> editModeRows = new HashMap<Integer, Boolean>();
	protected boolean editMode;
	private boolean searchMode;
	private boolean multiSearchMode;

	// getter and setter methods
	// ---------------------------------------------------------------------------------
	/**
	 * @return Page number of current page.
	 */
	public int getCurrentPage() {
		int rows = dataTable.getRows();
		// Prevent ArithmeticException: / by zero.
		if (rows != 0) {
			int first = dataTable.getFirst();
			int count = dataTable.getRowCount();
			return (count / rows) - ((count - first) / rows) + 1;
		} else {
			return 1;
		}
	}

	/**
	 * @return Total amount of pages.
	 */
	public int getTotalPages() {
		int rows = dataTable.getRows();
		if (rows != 0) { // Prevent ArithmeticException: / by zero.
			int count = dataTable.getRowCount();
			return (count / rows) + ((count % rows != 0) ? 1 : 0);
		} else {
			return 1;
		}
	}

	/**
	 * @return an instance of jsf datatable
	 */
	public HtmlDataTable getDataTable() {
		if (dataTable == null) {
			dataTable = new HtmlDataTable();
			dataTable.setRows(DEFAULT_TABLE_ROWS);
		}
		return dataTable;
	}

	/**
	 * @return name of sorted field.
	 */
	public String getSortField() {
		return sortField;
	}

	/**
	 * @Notice: date is defined in inherited class
	 * @return a list of date
	 */
	public List<E> getDataList() {
		return dataList;
	}

	/**
	 * @return boolean type
	 */
	public boolean isSortAscending() {
		return sortAscending;
	}

	/**
	 * If multiple rows of tabe are selected
	 */
	public boolean isSelectMultiple() {
		return selectMultiple;
	}

	/**
	 * If select all rows
	 */
	public boolean isSelectAll() {
		return selectAll;
	}

	/**
	 * If a row selected
	 */
	public boolean isSelectedRow() {
		return isSelectedRow(dataTable.getRowIndex());
	}

	/**
	 * Check if a row selected
	 */
	protected boolean isSelectedRow(int index) {
		Boolean selected = selectedRows.get(index);
		return selected != null && selected;
	}

	/**
	 * @return If row in edit mode
	 */
	public boolean isEditModeRow() {
		return isEditModeRow(dataTable.getRowIndex());
	}

	/**
	 * used for non return method of isEditModeRow()
	 * @return boolean type
	 */
	protected boolean isEditModeRow(int index) {
		Boolean editMode = editModeRows.get(index);
		return editMode != null && editMode;
	}

	/**
	 * Edit: edit mode?
	 * 
	 * @return Edit mode?
	 */
	public boolean isEditMode() {
		return editMode;
	}

	/**
	 * Add: get count.
	 * 
	 * @return Number of new items to add.
	 */
	public int getAddCount() {
		return addCount;
	}

	/**
	 * Search data: search mode?
	 * 
	 * @return Search mode?
	 */
	public boolean isSearchMode() {
		return searchMode;
	}

	public void setSearchMode(boolean searchMode) {
		this.searchMode = searchMode;
	}

	public boolean isMultiSearchMode() {
		return multiSearchMode;
	}

	public void setMultiSearchMode(boolean multiSearchMode) {
		this.multiSearchMode = multiSearchMode;
	}

	/**
	 * Error handling: check error messages.
	 * 
	 * @return Any error message?
	 */
	public boolean isMessage() {
		return FacesContext.getCurrentInstance().getMessages().hasNext();
	}

	/**
	 * CRUD table: set datatable.
	 * 
	 * @param dataTable
	 *            The datatable.
	 */
	public void setDataTable(HtmlDataTable dataTable) {
		this.dataTable = dataTable;
	}

	/**
	 * Add: set amount of new data items to be added.
	 * 
	 * @param addCount
	 *            The amount of new data items to be added.
	 */
	public void setAddCount(int addCount) {
		this.addCount = addCount;
	}

	/**
	 * Select: set selection.
	 * 
	 * @param Selected
	 *            row?
	 */
	public void setSelectedRow(boolean selected) {
		selectedRows.put(dataTable.getRowIndex(), selected);
	}

	/**
	 * Select by radio button: catch selected data item.
	 * 
	 * @param event
	 *            The value change event. This parameter is never used.
	 */
	public void setSelectedItem(ValueChangeEvent event) {
		selectedRows.put(dataTable.getRowIndex(), true);
	}

	/**
	 * Search data: set search example to search on.
	 * 
	 * @param searchExample
	 *            The search example to search on.
	 */

	// Common Form actions
	// -------------------------------------------------------------------------------

	/**
	 * Load data. definition of query is also setup in its children.
	 */
	public abstract void actionLoad();

	/**
	 * Search data. Toggle search mode, load data and go to first page.
	 */
	public void actionSearch() {
		setSearchMode(true);
		actionLoad();
		pageFirst();
	}

	/**
	 * Reset status of selected rows and sort data.
	 */
	public void actionSort(ActionEvent event) {

		// Get and set sort field and sort order.
		if (event != null) {
			String sortFieldAttribute = getAttribute(event, "sortField");

			if (sortField != null && sortField.equals(sortFieldAttribute)) {
				sortAscending = !sortAscending;
			} else {
				sortField = sortFieldAttribute;
				sortAscending = true;
			}
		}

		if (sortField != null) {
			Collections.sort(dataList, new DTOComparator(sortField,
					sortAscending));
			// Clear row modes.
			editModeRows.clear();
			selectedRows.clear();
		}
	}

	/**
	 * Add one record each time able to add muliple records one time, but
	 * blocked
	 */
	public void actionAdd(E dataItem) {
		dataList.add(dataItem);
		editModeRows.put(dataList.size() - 1, true);
		editMode = true;
		// Reset counter and go to last page.
		addCount = DEFAULT_ADD_COUNT;
		pageLast();
	}

	/**
	 * Edit items able to edit more than one items at the same time
	 */
	public void actionEdit() {
		// Toggle edit mode on selected items.
		editModeRows.putAll(selectedRows);
		selectedRows.clear();
		if (!editModeRows.containsValue(Boolean.TRUE)) {
			setErrorMessage("Select at least one item to edit.");
		} else {
			editMode = true;
		}
	}

	/**
	 *save new added entries or modified exiting entry
	 */
	public abstract void actionSave();;

	/**
	 * Delete selected items.
	 */
	public abstract void actionDelete();

	/**
	 * Select all items.
	 */
	public void actionSelectAll() {
		// Toggle.
		selectAll = !selectAll;
		if (selectAll) {
			selectMultiple = true;
		}
		// Set selection.
		for (int index = 0; index < dataList.size(); index++) {
			selectedRows.put(index, selectAll);
		}
	}

	/**
	 * Toggle select type between multiple (checkboxes) and single
	 * (radiobuttons).
	 */
	public void actionToggleSelect() {

		// Toggle.
		selectMultiple = !selectMultiple;
		if (!selectMultiple) {
			selectAll = false;
		}
		// Reset selected items.
		selectedRows.clear();
	}

	/**
	 * Refresh items.
	 */
	public void actionRefresh() {
		// Reset settings.
		setSearchMode(false);
		setMultiSearchMode(false);
		editMode = false;
		dataTable.setRows(dataTable.getRows());
		sortField = DEFAULT_SORT_FIELD;
		sortAscending = DEFAULT_SORT_ASCENDING;
		selectMultiple = DEFAULT_SELECT_MULTIPLE;
		selectAll = DEFAULT_SELECT_ALL;
		addCount = DEFAULT_ADD_COUNT;
		// Reload data and go to first page.
		actionLoad();
		pageFirst();
	}

	// Paging actions
	// -----------------------------------------------------------------------------

	/**
	 * Paging datatable: go to first page.
	 */
	public void pageFirst() {
		dataTable.setFirst(0);
		// log(dataTable.getFirst());
	}

	/**
	 * Paging datatable: go to previous page.
	 */
	public void pagePrevious() {
		int first = dataTable.getFirst() - dataTable.getRows();
		if (first < 0) { // Prevent IllegalArgumentException from HtmlDataTable.
			first = 0;
		}
		dataTable.setFirst(first);
	}

	/**
	 * Paging datatable: go to next page.
	 */
	public void pageNext() {
		int first = dataTable.getFirst() + dataTable.getRows();
		if (dataTable.getRows() > dataTable.getRowCount()) {
			first = 0;
		} else if (first >= dataTable.getRowCount()) {
			first = dataTable.getRowCount() - 1;
		}
		dataTable.setFirst(first);
	}

	/**
	 * Paging datatable: go to last page.
	 */
	public void pageLast() {
		int rows = dataTable.getRows();
		if (rows != 0) { // Prevent ArithmeticException: / by zero.
			int count = dataTable.getRowCount();
			dataTable.setFirst(count
					- ((count % rows != 0) ? count % rows : rows));
		}
	}

	// Helpers
	// ------------------------------------------------------------------------------------

	/**
	 * Set global error message.
	 * 
	 * @param errorMessage
	 *            The error message.
	 */
	protected static void setErrorMessage(String errorMessage) {
		// log(errorMessage);
		FacesContext.getCurrentInstance().addMessage(null,
				new FacesMessage(errorMessage));
	}

	/**
	 * Get attribute value from the component using action event and attribute
	 * name.
	 * 
	 * @param event
	 *            The action event.
	 * @param name
	 *            The attribute name.
	 */
	private static String getAttribute(ActionEvent event, String name) {
		String value = (String) event.getComponent().getAttributes().get(name);
		// log("Name=" + name + ", Value=" + value);
		return value;
	}

	/**
	 * Error handling: focus error field using Javascript.
	 * 
	 * @return Client ID of field to focus on.
	 */
	public String getFocus() {
		if (isMessage()) {
			return FacesContext.getCurrentInstance().getClientIdsWithMessages()
					.next();
		} else if (isEditMode()) {
			for (Entry<Integer, Boolean> entry : editModeRows.entrySet()) {
				if (entry.getValue()) {
					return dataTable.getClientId(FacesContext
							.getCurrentInstance())
							+ ":" + entry.getKey() + ":name"; // Focus on 'name'
					// field of 1st
					// editmode row.
				}
			}
		}
		return null;
	}

	/**
	 * Error handling: highlight error fields using Javascript.
	 * 
	 * @return Client IDs with error message, comma separated.
	 */
	public String getClientIdsWithMessages() {
		StringBuilder clientIdsWithMessages = new StringBuilder();
		Iterator<String> iter = FacesContext.getCurrentInstance()
				.getClientIdsWithMessages();
		while (iter.hasNext()) {
			clientIdsWithMessages.append(iter.next());
			if (iter.hasNext()) {
				clientIdsWithMessages.append(",");
			}
		}
		return clientIdsWithMessages.toString();
	}
}
